package leetcode.editor.cn.dsa25_toposort;
//你这个学期必须选修 numCourse 门课程，记为 0 到 numCourse-1 。
// 在选修某些课程之前需要一些先修课程。 例如，想要学习课程 0 ，你需要先完成课程 1 ，我们用一个匹配来表示他们：[0,1]
// 给定课程总量以及它们的先决条件，请你判断是否可能完成所有课程的学习？
//
// 示例 1:
// 输入: 2, [[1,0]]
//输出: true
//解释: 总共有 2 门课程。学习课程 1 之前，你需要完成课程 0。所以这是可能的。 
//
// 示例 2: 
// 输入: 2, [[1,0],[0,1]]
//输出: false
//解释: 总共有 2 门课程。学习课程 1 之前，你需要先完成​课程 0；并且学习课程 0 之前，你还应先完成课程 1。这是不可能的。 
//
// 提示： 
// 输入的先决条件是由 边缘列表 表示的图形，而不是 邻接矩阵 。详情请参见图的表示法。
// 你可以假定输入的先决条件中没有重复的边。 
// 1 <= numCourses <= 10^5 
// 
// Related Topics 深度优先搜索 广度优先搜索 图 拓扑排序 
// 👍 670 👎 0

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class CourseSchedule207 {
    public static void main(String[] args) {
        Solution solution = new CourseSchedule207().new Solution();
        int[][] arr = {{1,0}};
        System.out.println(solution.canFinish(2, arr)); //true
        int[][] arr2 = {{1,0},{0,1}};
        System.out.println(solution.canFinish(2, arr2)); //false
        int[][] arr3 = {{1,0},{2,1},{3,4},{4,3}};
        System.out.println(solution.canFinish(5, arr3)); //false
    }

    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        // 使用拓扑排序
        public boolean canFinish(int numCourses, int[][] prerequisites) {
            int m = prerequisites.length;
            if (m == 0) return true;
            int n = prerequisites[0].length;
            // 统计所有节点的入度
            int[] indegrees = new int[numCourses];
            ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
            for (int i = 0; i < numCourses; i++) {
                adj.add(new ArrayList<>());
            }
            for (int i = 0; i < m; i++) {
                adj.get(prerequisites[i][1]).add(prerequisites[i][0]);
                indegrees[prerequisites[i][0]]++;
            }
            // 将入度为0的节点入队
            Queue<Integer> queue = new LinkedList<>();
            for (int i = 0; i < numCourses; i++) {
                if (indegrees[i] == 0) queue.offer(i);
            }
            // 节点依次出队，删除它们的所有的出边，并将新产生的入度为0的节点入队
            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    int course = queue.poll();
                    for (int num : adj.get(course)) {
                        if (--indegrees[num] == 0) queue.offer(num);
                    }
                }
            }
            // 如果存在入度大于0的节点，说明排序失败，即存在环。返回false
            for (int i = 0; i < numCourses; i++) {
                if (indegrees[i] > 0) return false;
            }
            return true;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
}